const blogDao = require("../dao/blogDao");
const blogTypeDao = require("../dao/blogTypeDao");
const { validate } = require("validate.js");
const { ValidationError } = require("../utils/errors");
const { Op } = require("sequelize");
const generateBlogToc = require("../utils/generateBlogToc");
const { deleteCommentByBlogId } = require("../dao/commentDao");


// 自定义验证，验证分类id是否存在
validate.validators.isExistBlogTypeId = async (value, options, key, attribute) => {
    const result = await blogTypeDao.findOne(value);
    if (result) {
        return null;
    }
    else {
        return "categroyId is not exist";
    }
}

/**
 * 获取单篇文章
 * @param {Number} id 文章id 
 */
exports.getOneBlogService = async (id, headers) => {
    const data = await blogDao.findOnBlogDao(id);
    if (data && data.dataValues) {
        delete data.dataValues.categroyId;
        data.dataValues.toc = JSON.parse(data.dataValues.toc);

        // 根据请求头中的 authenication 字段判断是否为前端获取，如果是则需要更新浏览量
        if (!headers.authorization) {
            blogDao.increaseBlogScanNumber(id);
        }

        return data.dataValues;
    }

    return data;
}

/**
 * 添加一篇文章
 * @param {Object} blogData 文章对象
 */
exports.addOneBlogService = async (blogData) => {
    // 定义验证规则
    const rule = {
        title: {
            type: "string",
            presence: {
                allowEmpty: false,
            }
        },
        description: {
            type: "string",
            presence: {
                allowEmpty: false
            }
        },
        htmlContent: {
            type: "string",
            presence: {
                allowEmpty: false
            }
        },
        // thumb: {
        //     type: "string",
        //     presence: {
        //         allowEmpty: false
        //     }
        // },
        categoryId: {
            type: "number",
            isExistBlogTypeId: "true",
            presence: {
                allowEmpty: false
            }
        }
    }

    // 1. 异步验证
    try {
        await validate.async(blogData, rule);
    } catch (err) {
        console.log(err);
        throw new ValidationError("数据格式不正确");

    }

    // 2. 初始化一些数据
    blogData.createDate = Date.now();
    blogData.scanNumber = 0;
    blogData.commentNumber = 0;

    // 3. 经过这个函数处理，会在 blogData 对象中生成正确格式的 Toc
    generateBlogToc(blogData);

    // 4. 同时更新文章分类表的数据
    await blogTypeDao.increaseBlogCount(blogData.categoryId);

    // 5. 插入记录，并返回结果
    const { id } = await blogDao.createOneBlogDao(blogData);
    const { dataValues } = await blogDao.findOnBlogDao(id);
    delete dataValues.categoryId;
    return dataValues;
}

/**
 * 删除一篇文章
 * @param {Number} id 文章id
 * @returns 
 */
exports.deleteOnBlogService = async (id) => {

    // 1. 根据文章id获取到对应的分类id，让对应的分类的文章数-1
    const { dataValues } = await blogDao.findOnBlogDao(id);
    await blogTypeDao.minusBlogCount(dataValues.categoryId);

    // 2. 删除评论
    await deleteCommentByBlogId(id);

    // 3. 删除文章
    const result = await blogDao.deleteOneBlogDao(id);

    // 4. 转为布尔值返回
    return !!result;
}

/**
 * 修改文章
 * @param {Object} newData 文章对象
 * @param {Number} id 文章id
 * @returns 
 */
exports.updateOneBlogService = async (newData, id) => {
    // 1.客户端客户可能会传递一个空数组的toc属性进来，因为toc是由服务器动态生成的，所以这里我们直接把这个属性删除掉就ok
    delete newData.toc;

    // 2. 判断是否修改主体内容，如果修改需要重新生成 toc。根据是否传入htmlContent来判断，如果没有修改客户端也传了这个属性，那也没办法
    if (newData.htmlContent) {
        generateBlogToc(newData);
    }

    // 3. 更新数据
    blogDao.updateOneBlogDao(newData, id);
    
    // 4. 重新将数据查询出来
    const { dataValues } = await blogDao.findOnBlogDao(id);

    // 5. 判断是否修改了文章分类，如果修改了则需要更新文章分类的文章数
    if (dataValues.categoryId !== newData.categoryId) {
        const oldBlogTypeData = await blogTypeDao.findOne(dataValues.categoryId);
        const newBlogTypeData = await blogTypeDao.findOne(newData.categoryId);
        oldBlogTypeData.articleCount--;
        newBlogTypeData.articleCount++;
        await oldBlogTypeData.save();
        await newBlogTypeData.save();
    }

    delete dataValues.categoryId;
    return dataValues;
}

/**
 * 分页获取文章
 * @param {number} page 
 * @param {number} limit 
 * @param {string} keyword 
 * @param {number} categroyId 
 * @returns 
 */
exports.pagingGetBlogService = async (page = 1, limit = 10, keyword, categroyId = -1) => {
    // 定义好where对象
    const where = {};
    if (categroyId && categroyId !== -1) {
        where.categroyId = categroyId;
    }
    if (keyword) {
        where.title = {
            [Op.like]: `%${keyword}%`
        }
    }
    if (page < 1) page = 1;
    if (limit < 1) limit = 1;
    const data = await blogDao.pagingFindBlogDao((page - 1) * limit, +limit, where);

    // 组装数据对象
    const result = {
        total: data.count,
        rows: data.rows.map(item => {
            item.dataValues.toc = JSON.parse(item.dataValues.toc);
            return item.dataValues;
        })
    }
    return result;
}